package Utils.db;

import Utils.reflect.ReflectUtils;
import Utils.converters.Data2SqlDataType;
import Utils.converters.DataType;
import Utils.converters.MyUtils;
import com.alibaba.druid.pool.DruidDataSource;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Jdbc工具类
 *
 * @author d11yu
 */
public final class JdbcUtilsStatic {
    private static DruidDataSource dataSource;
    private static ThreadLocal<Connection> tlConn = new ThreadLocal<>();

    static {
        try {
            dataSource = (DruidDataSource) DruidUtils.getInstance("jdbc.properties").getDds();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //region 自定义的通用方法

    /**
     * 获取Connection，如果返回null，则说明获取失败。
     *
     * @return
     */
    public static Connection getConnection() {
        Connection conn = tlConn.get();
        if (conn == null) {
            try {
                conn = dataSource.getConnection();
                tlConn.set(conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn;
    }

    /**
     * 关闭数据库链接及资源
     *
     * @param conn
     * @param statement
     */
    public static void close(Connection conn, Statement statement) {
        close(statement);
        close(conn);
    }

    /**
     * commit并关闭数据库连接
     */
    public static void commitAncClose() {
        commitAncClose(tlConn);
    }

    /**
     * commit并关闭数据库连接
     *
     * @param threadLocal 包含connection的threadLocal
     */
    public static void commitAncClose(ThreadLocal<Connection> threadLocal) {
        Connection conn = threadLocal.get();
        if (conn != null) {
            try {
                conn.commit();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        // 一定要执行remove操作，否则会出错。（因为Tomcat服务器底层使用了夏娜城池）
        threadLocal.remove();
    }

    /**
     * rollback并关闭数据库连接
     */
    public static void rollbackAncClose() {
        rollbackAncClose(tlConn);
    }

    /**
     * rollback并关闭数据库连接
     *
     * @param threadLocal 包含connection的threadLocal
     */
    public static void rollbackAncClose(ThreadLocal<Connection> threadLocal) {
        Connection conn = threadLocal.get();
        if (conn != null) {
            try {
                conn.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        // 一定要执行remove操作，否则会出错。（因为Tomcat服务器底层使用了线程池）
        threadLocal.remove();
    }

    /**
     * 关闭数据库链接及资源
     *
     * @param conn
     */
    public static void close(Connection conn, Statement statement, ResultSet rs) {
        close(rs);
        close(conn, statement);
    }

    /**
     * 关闭statement资源
     *
     * @param statement
     */
    public static void close(Statement statement) {
        try {
            if (statement != null) {
                statement.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭ResultSet资源
     *
     * @param rs
     */
    public static void close(ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭Connection资源
     *
     * @param conn
     */
    public static void close(Connection conn) {
        try {
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭threadLocal中的数据库连接
     *
     * @param threadLocal
     */
    public static void close(ThreadLocal<Connection> threadLocal) {
        close(threadLocal.get());
    }

    /**
     * 修改数据库（增、删、改），操作完后，不关闭conn（效率较低，批量操作时不推荐，事务安全）。
     *
     * @param sql  sql语句
     * @param conn 数据库链接
     * @param args SQL语句中的参数
     * @return 返回影响的行数，如果执行失败，则返回结果为0。
     */
    public static int modify(String sql, Connection conn, Object... args) throws SQLException {
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement(sql);
            fillStatement(ps, args);
            /*ps.execute():
            如果执行的是查询操作，有返回结果，则此方法返回true;
            如果执行的是增、删、改操作，没有返回结果，则此方法返回false.
            */
            return ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(ps);
        }
        return 0;
    }

    /**
     * 修改数据库（增、删、改），操作完后，不关闭conn、PreparedStatement。
     *
     * @param ps   PreparedStatement，需要先处理好SQL语句。
     * @param args SQL语句中的参数
     * @return 返回影响的行数，如果执行失败，则返回结果为0。
     */
    public static int modify(PreparedStatement ps, Object... args) {
        try {
            fillStatement(ps, args);
            /*ps.execute():
            如果执行的是查询操作，有返回结果，则此方法返回true;
            如果执行的是增、删、改操作，没有返回结果，则此方法返回false.
            */
            return ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 修改数据库（增、删、改），操作完后，不关闭conn、PreparedStatement。
     *
     * @param ps        PreparedStatement，需要事先处理好SQL语句。
     * @param converter 转换器
     * @param args      SQL语句中的参数
     * @return 返回影响的行数，如果执行失败，则返回结果为0。
     */
    public static int modifySmart(PreparedStatement ps, Data2SqlDataType converter, Object... args) throws Exception {
        try {
            fillStatementSmart(ps, converter, args);
            /*ps.execute():
            如果执行的是查询操作，有返回结果，则此方法返回true;
            如果执行的是增、删、改操作，没有返回结果，则此方法返回false.
            */
            return ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 修改数据库（增、删、改），操作完后，执行完后会关闭资源。（效率较低，高频操作请谨慎使用）
     *
     * @param sql  sql语句
     * @param args SQL语句中的参数
     * @return 返回影响的行数，如果执行失败，则返回结果为0。
     */
    public static int modify(String sql, Object... args) throws SQLException {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = getConnection();
            ps = conn.prepareStatement(sql);
            return modify(ps, args);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(conn, ps);
        }
        return 0;
    }

    /**
     * 根据list批量修改数据库。
     *
     * @param sql        SQL语句，结尾不要有分号（";"）
     * @param conn       数据库连接
     * @param converter  转换器
     * @param list       存放数据的list
     * @param fieldNames Java bean对象的字段
     * @param <T>
     */
    public static <T> void modifyPerContent(String sql, Connection conn, Data2SqlDataType converter, List<T> list, String[] fieldNames) {
        PreparedStatement ps = null;
        try {
            boolean isAutoCommit = conn.getAutoCommit();
            conn.setAutoCommit(false);
            ps = conn.prepareStatement(sql);
            int thresh = 500;
            int end = list.size() - 1;
            for (int i = 0; i < list.size(); i++) {
                Object[] values = ReflectUtils.getDeclaredFieldValues(list.get(i), fieldNames);
                fillStatementSmart(ps, converter, values);
                ps.addBatch();
                if (i % thresh == 0 || i == end) {
                    ps.executeBatch();
                    ps.clearBatch();
                }
            }
            conn.commit();
            conn.setAutoCommit(isAutoCommit);
        } catch (Exception e) {
            e.printStackTrace();
            try {
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            close(ps);
        }
    }

    /**
     * 根据list批量修改数据库。
     *
     * @param sql        SQL语句
     * @param converter  转换器
     * @param list       存放数据的list
     * @param fieldNames Java bean对象的字段
     * @param <T>
     */
    public static <T> void modifyPerContent(String sql, Data2SqlDataType converter, List<T> list, String[] fieldNames) {
        Connection conn = null;
        try {
            conn = getConnection();
            modifyPerContent(sql, conn, converter, list, fieldNames);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(conn);
        }
    }

    /**
     * 对PrepareStatement进行参数填充。
     *
     * @param ps   待处理的PrepareStatement。
     * @param args 传入SQL语句的参数
     * @throws SQLException
     */
    public static void fillStatement(PreparedStatement ps, Object... args) throws SQLException {
        for (int i = 0; i < args.length; i++) {
            ps.setObject(i + 1, args[i]);
        }
    }

    /**
     * 对PrepareStatement进行参数填充。
     *
     * @param ps   待处理的PrepareStatement。
     * @param converter
     * @param args 传入SQL语句的参数
     * @throws SQLException
     */
    public static void fillStatementSmart(PreparedStatement ps, Data2SqlDataType converter, Object... args) throws Exception {
        for (int i = 0; i < args.length; i++) {
            DataType dataType = MyUtils.getDataType(args[i]);
            switch (dataType) {
                case DOUBLE:
                case INT:
                case STRING:
                case BOOLEAN:
                default:
                    ps.setObject(i + 1, args[i]);
                    break;
                case DATE_UTIL:
                    ps.setObject(i + 1, converter.invoke(Date.class, args[i]));
                    break;
                case DATE_SQL:
                    ps.setObject(i + 1, args[i]);
                    break;
            }
        }
    }

    /**
     * 执行查询语句，如果查询结果为空，则返回null。
     *
     * @param clazz 返回bean的Class
     * @param sql   SQL语句。注意：
     *              针对于表的字段名与类的属性名不相同的情况：
     *              1.必须声明sql时，使用类的属性名来命名字段的别名
     *              2.使用ResultSetMetaData日寸，需要使用getColumnLabel()来替换getColumnName(),
     *              获取列的别名。
     *              说明：如果sql中没有给字段其别名，getColumnLabel()获取的就是列名	I
     * @param args  传入SQL语句的参数
     * @param <T>   bean对象的泛型类型
     * @return
     */
    public static <T> ArrayList<T> query(Class<T> clazz, String sql, Object... args) {
        Connection conn = null;
        try {
            conn = getConnection();
            return query(clazz, conn, sql, args);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(conn);
        }
        return null;
    }

    /**
     * 执行查询语句，如果查询结果为空，则返回null。
     *
     * @param clazz 返回bean的Class
     * @param conn  数据库连接
     * @param sql   SQL语句。注意：
     *              针对于表的字段名与类的属性名不相同的情况：
     *              1.必须声明sql时，使用类的属性名来命名字段的别名
     *              2.使用ResultSetMetaData时，需要使用getColumnLabel()来替换getColumnName(),
     *              获取列的别名。
     *              说明：如果sql中没有给字段其别名，getColumnLabel()获取的就是列名	I
     * @param args  传入SQL语句的参数
     * @param <T>   bean对象的泛型类型
     * @return
     */
    public static <T> ArrayList<T> query(Class<T> clazz, Connection conn, String sql, Object... args) {
        ArrayList<T> res = new ArrayList<>();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = getPreparedStatement(sql, conn, args);
            rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int colCount = rsmd.getColumnCount();
            while (rs.next()) {
                T t = clazz.newInstance();
                for (int i = 0; i < colCount; i++) {
                    Object colValue = rs.getObject(i + 1);
                    // 获取列的列名（不推荐使用）
//                    String colName = rsmd.getColumnName(i + 1);
                    // 获取列的别名
                    String colName = rsmd.getColumnLabel(i + 1);
                    ReflectUtils.setDeclaredField(t, colName, colValue);
                }
                res.add(t);
            }
            return res;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(rs);
            close(ps);
        }
        return null;
    }

    /**
     * 获取执行SQL注入后的PreparedStatement
     *
     * @return
     * @throws SQLException
     */
    public static PreparedStatement getPreparedStatement(String sql, Connection conn, Object... args) throws SQLException {
        PreparedStatement ps = getPreparedStatement(sql, conn);
        fillStatement(ps, args);
        return ps;
    }

    /**
     * 获取PreparedStatement
     *
     * @param sql  SQL语句
     * @param conn 数据库链接
     * @return
     * @throws SQLException
     */
    public static PreparedStatement getPreparedStatement(String sql, Connection conn) throws SQLException {
        return conn.prepareStatement(sql);
    }

    /**
     * 用于查询特殊值的方法。比如：select count(*) from ...
     *
     * @param sql
     * @param args
     * @return
     */
    public static <T> T getValue(String sql, Object... args) {
        Connection conn = null;
        try {
            conn = getConnection();
            return getValue(conn, sql, args);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close(conn);
        }
        return null;
    }

    /**
     * 用于查询特殊值的方法。比如：select count(*) from ...
     *
     * @param conn
     * @param sql
     * @param args
     * @param <T>
     * @return
     */
    public static <T> T getValue(Connection conn, String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = getPreparedStatement(sql, conn, args);
            rs = ps.executeQuery();
            if (rs.next()) {
                return (T) rs.getObject(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(ps);
            close(rs);
        }
        return null;
    }
    //endregion
}